Creating the iSDIO extension command

Latest update: November 2014

This tutorial covers the creation of a routine to issue iSDIO commands, before we move on to operating the FlashAir card.

About the iSDIO command

In order to issue commands, we'll use the Extension Command Registers CMD48 (READ_EXTR_SINGLE) and CMD49 (WRITE_EXTR_SINGLE), which are defined in Function Extension Commands (class 11) of SD standard(*1).

CMD48 and CMD49 have three modes:

  • Register: Read/Write to memory with a specific address and length, used for small amounts of data like status updates.
  • Data Port: Read/Write a 512 byte fixed memory block, used for large amounts of data.
  • Mask Writing (CMD49 only): Changes part of one specific byte, used for RESET status.

The iSDIO Wireless LAN Simplified Addendum regulates which mode should be used where (e.g. Register mode for address area A, Data Port mode for address area B).

Add CMD48/49 to Arduino

The Arduino IDE has an SD card library available by default, which consists of three modules:

  • A Class that communicates with the SD card via SPI ( Sd2Card)
  • A Class that access the FAT file sysytem on the SD card ( SdFat)
  • A Class that reads/writes files to the card (combines the above) ( SD)

For this tutorial we're only going to need Sd2Card, however out of the box it doens't support CMD48/49. So lets add them!

Making an iSDIO module

When you finish installing Arduino IDE, you will see an "Arduino" folder in the Documents directory. Inside that is a "libraries" directory.

We'll be copying Sd2Card here, then adding iSDIO support to create our own module.

The Directory Structure will look something like the following:

<Document>/
+-- Arduino
    +-- libraries
        +-- iSDIO
            +-- iSdio.h *1
            +-- iSdio.cpp *2
            +-- utility
                +-- Sd2Card.h *1
                +-- Sd2Card.cpp *1
                +-- Sd2CardExt.h *2
                +-- Sd2CardExt.cpp *2
                +-- Sd2PinMap.h *1
                +-- SdInfo.h *1

Files marked with 1 are copied from the base version ( Program Files (x86)\Arduino\libraries\SD\utility). We'll be making a small change to Sd2Card.h.

Files marked with 2 are going to be created now.

Making the Sd2CardExt class

We're going to make an Sd2CardExt class by extending the base Sd2Card class and adding CMD48/49. We're calling it Sd2CardExt because CMD48/49 are technically part of the SD standard, not the iSDIO standard.

We're adding the CMD48/49 modes we discussed above (lines 10-14), and then two basic read/write functions (lines 16 and 17).

Sd2CardExt.h

#ifndef Sd2CardExt_h
#define Sd2CardExt_h

#include "Sd2Card.h"

class Sd2CardExt : public Sd2Card {
 public:
  Sd2CardExt(void) : Sd2Card() {}

  uint8_t readExtDataPort(uint8_t mio, uint8_t func, uint16_t addr, uint8_t* dst);
  uint8_t readExtMemory(uint8_t mio, uint8_t func, uint32_t addr, uint16_t count, uint8_t* dst);
  uint8_t writeExtDataPort(uint8_t mio, uint8_t func, uint16_t addr, const uint8_t* src);
  uint8_t writeExtMemory(uint8_t mio, uint8_t func, uint32_t addr, uint16_t count, const uint8_t* src);
  uint8_t writeExtMask(uint8_t mio, uint8_t func, uint32_t addr, uint8_t mask, const uint8_t* src);
 protected:
  uint8_t readExt(uint32_t arg, uint8_t* src, uint16_t count);
  uint8_t writeExt(uint32_t arg, const uint8_t* src, uint16_t count);
};
#endif  // Sd2CardExt_h

Sd2Card.h

To inherit the Sd2Card class, Sd2Card.h needs to use "protected". See below.

Before:

 ...
 private:
  uint32_t block_;
  uint8_t chipSelectPin_;
  uint8_t errorCode_;
 ...

After:

 ...
 protected:
  uint32_t block_;
  uint8_t chipSelectPin_;
  uint8_t errorCode_;
 ...

Sd2CardExt.cpp (Part 1)

First, add constants related to CMD48/49.

#include <Arduino.h>
#include "Sd2CardExt.h"

uint8_t const CMD48 = 0x30;
uint8_t const CMD49 = 0x31;
uint8_t const SD_CARD_ERROR_CMD48 = 0x80;
uint8_t const SD_CARD_ERROR_CMD49 = 0x81;

Next, make functions to send/receive data using SPI. This will be the same as the version in the Sd2Card module, so we're going to use inline to copy it (because it will be used frequently).

Sd2CardExt.cpp (Part 2)

/** Send a byte to the card */
inline void spiSend(uint8_t b) {
  SPDR = b;
  while (!(SPSR & (1 << SPIF)));
}

/** Receive a byte from the card */
inline  uint8_t spiReceive(void) {
  spiSend(0xFF);
  return SPDR;
}

Next, we'll make the common readExt() and writeExt() functions. These will issue CMD48/49 commands using readData() and writeBlock(), which are functions for CMD17/24 included in the base Sd2Card class.

The signal read/write timing of CMD48/49 is the same as CMD17/24 for general memory access, however the command number and parameters are different and CMD48/49 read/writes are always in 512 byte blocks.

If you want to read/write less than 512 bytes you'll need to add padding.

In readExt(), one byte of dummy data is sent after chipSelectHigh(). This is to ensure that command processing starts properly.

Sd2CardExt.cpp (part 3)

/** Perform Extention Read. */
uint8_t Sd2CardExt::readExt(uint32_t arg, uint8_t* dst, uint16_t count) {
  uint16_t i;

  // send command and argument.
  if (cardCommand(CMD48, arg) && cardCommand(CMD17, arg)) {
    error(SD_CARD_ERROR_CMD48);
    goto fail;
  }

  // wait for start block token.
  if (!waitStartBlock()) {
    goto fail;
  }

  // receive data
  for (i = 0; i < count; ++i) {
    dst[i] = spiReceive();
  }

  // skip dummy bytes and 16-bit crc.
  for (; i < 514; ++i) {
    spiReceive();
  }

  chipSelectHigh();
  spiSend(0xFF); // dummy clock to force FlashAir finish the command.
  return true;

 fail:
  chipSelectHigh();
  return false;
}

/** Perform Extention Write. */
uint8_t Sd2CardExt::writeExt(uint32_t arg, const uint8_t* src, uint16_t count) {
  uint16_t i;
  uint8_t status;

  // send command and argument.
  if (cardCommand(CMD49, arg) && cardCommand(CMD24, arg)) {
    error(SD_CARD_ERROR_CMD49);
    goto fail;
  }

  // send start block token.
  spiSend(DATA_START_BLOCK);

  // send data
  for (i = 0; i < count; ++i) {
    spiSend(src[i]);
  }

  // send dummy bytes until 512 bytes.
  for (; i < 512; ++i) {
    spiSend(0xFF);
  }

  // dummy 16-bit crc
  spiSend(0xFF);
  spiSend(0xFF);

  // wait a data response token
  status = spiReceive();
  if ((status & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
    error(SD_CARD_ERROR_WRITE);
    goto fail;
  }

  // wait for flash programming to complete
  if (!waitNotBusy(SD_WRITE_TIMEOUT)) {
    error(SD_CARD_ERROR_WRITE_TIMEOUT);
    goto fail;
  }

  chipSelectHigh();
  return true;

 fail:
  chipSelectHigh();
  return false;
}

Lastly, we'll add functions to handle the three read/write modes. Make a parameter for each mode and pass it to readExt() and writeExt(), along with the data size.

mio is a flag to indicate memory area or I/O area. With iSDIO, the I/O area should always be flagged.

func is a function number. With iSDIO, function number 0 is reserved for general information, so the actual function number always starts at one. For example, FlashAir's wireless LAN function is 1.

addr is a memory address.

Address space is divided into 512 bytes "pages". In register mode, you can access individual parts of a page, but in data port mode the entire page is accessed at once. Also, cross-page access is not available.

count is the number of byte to access. If you're in data port mode, you must set it to 0.

For more information, see section 5.7.2 of SD Specifications Part 1 Physical Layer Simplified Specification.

Sd2CardExt.cpp (Part 4)

uint8_t Sd2CardExt::readExtDataPort(uint8_t mio, uint8_t func, 
        uint16_t addr, uint8_t* dst) {
  uint32_t arg = 
      (((uint32_t)mio & 0x1) << 31) | 
      (mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
      (((uint32_t)addr & 0x1FE00) << 9);

  return readExt(arg, dst, 512);
}

uint8_t Sd2CardExt::readExtMemory(uint8_t mio, uint8_t func, 
        uint32_t addr, uint16_t count, uint8_t* dst) {
  uint32_t offset = addr & 0x1FF;
  if (offset + count > 512) count = 512 - offset;

  if (count == 0) return true;

  uint32_t arg = 
      (((uint32_t)mio & 0x1) << 31) | 
      (mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
      ((addr & 0x1FFFF) << 9) |
      ((count - 1) & 0x1FF);

  return readExt(arg, dst, count);
}

uint8_t Sd2CardExt::writeExtDataPort(uint8_t mio, uint8_t func, 
          uint16_t addr, const uint8_t* src) {
  uint32_t arg =
      (((uint32_t)mio & 0x1) << 31) | 
      (mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
      (((uint32_t)addr & 0x1FE00) << 9);

  return writeExt(arg, src, 512);
}

uint8_t Sd2CardExt::writeExtMemory(uint8_t mio, uint8_t func, 
    uint32_t addr, uint16_t count, const uint8_t* src) {
  uint32_t arg =
      (((uint32_t)mio & 0x1) << 31) | 
      (mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
      ((addr & 0x1FFFF) << 9) |
      ((count - 1) & 0x1FF);

  return writeExt(arg, src, count);
}

uint8_t Sd2CardExt::writeExtMask(uint8_t mio, uint8_t func, 
    uint32_t addr, uint8_t mask, const uint8_t* src) {
  uint32_t arg =
      (((uint32_t)mio & 0x1) << 31) | 
      (mio ? (((uint32_t)func & 0x7) << 28) : (((uint32_t)func & 0xF) << 27)) |
      (0x1 << 26) |
      ((addr & 0x1FFFF) << 9) |
      mask;

  return writeExt(arg, src, 1);
}

Helper Functions!

iSDIO is a command protocol, it's meant to pass content for a whole range of applications. This means that unfortunately it's not the easiest to use, so lets make some helper functions to keep things simple and organized!

put\_xxx() writes the buffer and moves the pointer ahead by the buffer's size. It returns the new pointer as well. put\_xxx_arg() is for script parameters, and writes the size of "value", "value" itself, and padding to fit the 4 byte border. It will also move the pointer forwards by the number of bytes written and return it. put\_command\_header() and put\_command\_info\_header() are used to make headers for the script. It will also move the pointer forwards by the number of bytes written and return it. get\_xxx() reads from the buffer.

We will explain these functions in more detail in future tutorials.

Helper function

uint8_t* put_u8(uint8_t* p, uint8_t value);
uint8_t* put_u16(uint8_t* p, uint16_t value);
uint8_t* put_u32(uint8_t* p, uint32_t value);
uint8_t* put_str(uint8_t* p, const char* value);

uint8_t* put_u8_arg(uint8_t* p, uint8_t value);
uint8_t* put_u16_arg(uint8_t* p, uint16_t value);
uint8_t* put_u32_arg(uint8_t* p, uint32_t value);
uint8_t* put_str_arg(uint8_t* p, const char* value);

uint8_t* put_command_header(uint8_t* p, uint8_t num_commands,
        uint32_t command_bytes);
uint8_t* put_command_info_header(uint8_t* p, uint16_t command_id,
        uint32_t sequence_id, uint16_t num_args);

uint8_t get_u8(uint8_t* p);
uint16_t get_u16(uint8_t* p);
uint32_t get_u32(uint8_t* p);

How to use

Start the Arduino IDE, select Sketch > Use Library > Add Library ... from the menu, then set the iSDIO folder. Once that is done, when select Sketch > Use Library > iSDIO should appear in the menu automatically.

Execution

First, let's make sure the library is compiled properly.

Select File > New File from the menu and open new editor. Next, select Sketch > Use Library > iSDIO.

Make setup() function and blank loop() function to initialize the card.

arduino_tutorial_02.ino

#include <utility/Sd2CardExt.h>

const int chipSelectPin = 4;
Sd2CardExt card;

void setup() {
  // Initialize UART for message print.
  Serial.begin(9600);
  while (!Serial) {
    ;
  }

  // Initialize SD card.
  Serial.print(F("\nInitializing SD card..."));  
  if (card.init(SPI_HALF_SPEED, chipSelectPin)) {
    Serial.print(F("OK"));
  } else {
    Serial.print(F("NG"));
    abort();
  }
}

void loop() {
}

Then, make sure no errors occur with Sketch > Verification/Compilation.

Sample code

arduino_tutorial_02.zip (22KB)

All sample code on this page is licensed under BSD 2-Clause License.